package com.redis.lock;

import com.redis.lock.api.RLock;
import com.redis.lock.api.RedisCommd;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author zzm
 * @version V1.0
 * @date 2017-09-26 14:18
 **/
public class RedisLock implements RLock {
    private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
    private Thread thread;
    private String key;
    private volatile int state;
    private UUID id = UUID.randomUUID();
    private Random random = new Random();
    private int unlock_retry;
    //static final long spinForTimeoutThreshold = 1000L;

    public static RedisLock create(String key) {
        return create(key, 1);
    }

    public static RedisLock create(String key, int unlock_retry) {
        return new RedisLock(key, unlock_retry);
    }

    private RedisLock(String key, int unlock_retry) {
        this.key = key;
        this.unlock_retry = unlock_retry;
    }

    @Override
    public void lock(long expire, TimeUnit timeUnit) throws InterruptedException {
        if (expire <= 0L) throw new RuntimeException("expire least gt 0");
        String field = getLockName(Thread.currentThread().getId() + "");
        long result;
        for (; ; ) {
            result = RedisCommd.lock(key, field, timeUnit.toMillis(expire));
            //not hold lock
            if (result != 0L) {
                Thread.sleep(random.nextInt(10));
            } else {
                thread = Thread.currentThread();
                setState(getState() + 1);
                return;
            }
        }
    }

    @Override
    public boolean tryLock(long expire, TimeUnit timeUnit) {
        String field = getLockName(Thread.currentThread().getId() + "");
        long result = RedisCommd.lock(key, field, timeUnit.toMillis(expire));
        if (result == 0L){
            thread = Thread.currentThread();
            setState(getState() + 1);
            return true;
        }
        return false;
    }

    @Override
    public boolean tryLock(long timeout, long expire, TimeUnit timeUnit) throws InterruptedException {
        if (expire <= 0L) throw new RuntimeException("expire least gt 0");
        if (timeout <= 0L) throw new RuntimeException("timeout least gt 0");
        final long deadline = System.nanoTime() + timeUnit.toNanos(timeout);
        String field = getLockName(Thread.currentThread().getId() + "");
        long result;
        for (; ; ) {
            result = RedisCommd.lock(key, field, timeUnit.toMillis(expire));
            if (result != 0L) {
                if (deadline - System.nanoTime() <= 0L)
                    return false;
                Thread.sleep(random.nextInt(10));  //待优化,更优解
            } else {
                thread = Thread.currentThread();
                setState(getState() + 1);
                return true;
            }
        }
    }

    @Override
    public boolean isHeldByCurrentThread() {
        return thread == Thread.currentThread();
    }

    @Override
    public boolean isLocked() {
        return getState() != 0;
    }

    @Override
    public boolean unlock() {
        if (thread != Thread.currentThread()) throw new IllegalMonitorStateException();
        boolean free = false;
        int state = getState() - 1;
        if (state == 0) {
            free = true;
            thread = null;
        }
        setState(state);
        String field = getLockName(Thread.currentThread().getId() + "");
        for (int i = 0; i <= unlock_retry; i++) {
            long result = RedisCommd.unlock(key, field);
            if (result != -1)
                break;
            if (unlock_retry == i)
                logger.warn("当前线程解锁异常,线程ID:{}", Thread.currentThread().getId());
        }
        return free;
    }

    String getLockName(String threadId) {
        return this.id + ":" + threadId;
    }

    int getState() {
        return state;
    }

    void setState(int state) {
        this.state = state;
    }
}
